scroll

Key Blog

  • Key Homepage>
  • Blog>
  • [ue5] unreal insights를 활용한 최적화: async loading을 통한 게임 성능 향상
  • [UE5] Optimization Using Unreal Insights: Improving Game Performance with Asynchronous Loading

    @kiikey4(Key Zhao)

    [UE5] Optimization Using Unreal Insights: Improving Game Performance with Asynchronous Loading

    Last updated on Oct 20, 2024

    Posted on Oct 19, 2024

    1

    Summary

    In this article, we will introduce the process of conducting optimization load investigations using Unreal Insights, and how to implement asynchronous loading (background loading) in C++ as a countermeasure to prevent hitches that occur when assets are loaded synchronously with LoadSynchronous() in the game. For information on asynchronous loading in Blueprints, please refer to the links below.

    Environment

    • Rider 2024.2.6
    • Unreal Engine 5.4
    • Windows 11 Pro

    References

    Main Content

    Optimization Load Investigation Using Unreal Insights

    What is Unreal Insights?
    Unreal Insights is a tool for analyzing performance and memory usage in Unreal Engine. The official documentation can be found here: https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-in-unreal-engine

    How to Use
    For detailed usage, please refer to [UE5] Let's Try Using Unreal Insights.

    When conducting optimization investigations, it is ideal to use the packaged version of the target platform rather than the regular PIE (Play In Editor) mode. This is because the preloaded cache and background operations in PIE mode can interfere, making accurate load measurement difficult.

    This time, we will conduct the investigation in Standalone Game, which is closer to the packaged version environment than PIE.

    How to Launch Standalone Game

    Launch the Standalone Game from the UE Editor as shown in the figure below.

    how_to_launch_standalone_game_nbf9t2

    If hitches (stuttering) occur during gameplay, let's use the following command.

    stat_unitgraph_qvvmlg

    stat unitgraph

    The "stat UnitGraph" command is a tool that visualizes the processing load of the game in graph form. This allows you to clearly identify the problem areas, as large spikes will appear on the graph when hitches occur.

    Unlike the commonly used "stat Unit" or "stat fps," "stat UnitGraph" records even short-term hitches on the graph, so they are less likely to be missed.

    After execution, a graph like the one below will be displayed. The load graph is shown in the lower left, and specific values are displayed in the upper right. Ideally, to achieve 60 fps, it is best to keep the Frame time below 16.6ms.

    stat_unitgraph_explanation_mdv1fe

    Next, we will measure using Unreal Insights.

    trace_start_aq3qbs

    trace.start and trace.stop

    To start measuring with Unreal Insights, execute the trace.start command. When the measurement is complete, end it with trace.stop.

    Processing Load Measurement Results

    As seen in the video below, hitches have been confirmed.

    hitch_graph_nl0bb9

    When heavy processing is performed, significant changes appear on the graph.

    hitch_frame_gucxrj

    Game: 58.26ms
    We can see that the game thread took 58.26ms.

    Next, let's check the measurement results from Unreal Insights.

    How to Open Trace
    open_trace_ws9cha

    When opening the trace data, the following results are displayed. HitchhUnrealInsight_wlbcuz

    The key point in this result is indicated by the green bar:

    LoadObject (154.7ms) - /Game/Main/InGame/VFX/Niagara/NS_DizzyStar.NS_DizzyStar
    

    From this, we can see that the loading process of the Niagara effect is placing a significant load.

    Especially during the initial loading or spawning of Niagara, a hitch may occur due to shader compilation.

    This time, I'll explain the cause of the hitch during the initial loading, but if a hitch occurs during the initial spawning, it can be resolved by spawning in an invisible location beforehand (such as during a blackout or similar scene transition).

    In the packaged version, this hitch occurs only the first time after installation.
    In UE, it occurs only the first time after UE is started.

    If you want to reproduce the hitch, you need to restart Unreal Engine or delete and reinstall the package.

    This is the problematic Niagara effect.

    DizzyNiagaraEffect_x8gioz

    Looking at the C++ code, the loading process is as follows.

    PlayerCharacter.h
    1public: 2 //... 3 UPROPERTY(EditAnywhere, BlueprintReadWrite) 4 TSoftObjectPtr<UNiagaraSystem> DizzyEffectAsset; 5 //...
    PlayerCharacter.cpp
    1void APlayerCharacter::StartDizzy() 2{ 3 if (IsDizzy) 4 { 5 return; 6 } 7 CharacterMovementComponent->MaxWalkSpeed = DizzySpeed; 8 9 10 IsDizzy = true; 11 12 UNiagaraSystem* DizzyEffectSystem = DizzyEffectAsset.LoadSynchronous(); 13 if (!IsValid(DizzyEffectSystem)) 14 { 15 UE_LOG(LogTemp, Error, TEXT("DizzyEffectSystem is null, Function name: %s"), *FString(__FUNCTION__)); 16 } 17 DizzyEffect = UNiagaraFunctionLibrary::SpawnSystemAttached(DizzyEffectSystem, SceneComponent, NAME_None, 18 DizzyEffectOffset, FRotator::ZeroRotator, 19 EAttachLocation::KeepRelativeOffset, true); 20 21 if (!IsValid(DizzySoundAsset)) 22 { 23 UE_LOG(LogTemp, Error, TEXT("DizzySound is null, Function name: %s"), *FString(__FUNCTION__)); 24 } 25 else 26 { 27 DizzySound = UGameplayStatics::SpawnSoundAtLocation(GetWorld(), DizzySoundAsset, GetActorLocation()); 28 } 29 GetWorldTimerManager().SetTimer(DizzyTimerHandle, this, &APlayerCharacter::EndDizzy, DizzyDuration, false); 30}

    The hitch is caused by synchronously loading the effect asset right before spawning the effect using LoadSynchronous().

    LoadSynchronous() (synchronous load) waits for the load to complete (stopping other processes). This leads the player to experience a hitch.

    The official UE recommends asynchronous loading.

    Reference video: Maximizing Your Game's Performance in Unreal Engine | Unreal Fest 2022

    Asynchronous Loading is from 22:40 to 28:15 in the video.

    at 27:33, it shows how to implement asynchronous loading (AsyncLoad) in Blueprint.

    TSoftObjectPtr<UNiagaraSystem> DizzyEffectAsset
    The effect asset is held by the player.
    DizzyEffectAsset_wsa9ro

    Investigation Result: The cause is LoadSynchronous() just before spawning the effect.

    Implementing Asynchronous Loading (AsyncLoad)

    To eliminate the hitch caused by synchronous loading, we will pre-load the asset asynchronously (pre-read) at the start of the game.

    Asynchronous loading may take time, so please ensure there is enough time for loading, otherwise you cannot spawn the asset.

    Next, we will create a function for asynchronous loading in C++.

    PlayerCharacter.h
    1protected: 2 void OnDizzyEffectLoaded(); 3 void LoadDizzyEffectAsset();
    PlayerCharacter.cpp
    1void APlayerCharacter::BeginPlay() 2{ 3 Super::BeginPlay(); 4 LoadDizzyEffectAsset(); 5} 6 7void APlayerCharacter::LoadDizzyEffectAsset() 8{ 9 UE_LOG(LogTemp, Log, TEXT("DizzyEffectAsset requeset load")); 10 UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(DizzyEffectAsset.ToSoftObjectPath(), 11 FStreamableDelegate::CreateUObject( 12 this, &APlayerCharacter::OnDizzyEffectLoaded)); 13} 14 15void APlayerCharacter::OnDizzyEffectLoaded() 16{ 17 UE_LOG(LogTemp, Log, TEXT("DizzyEffectLoaded")); 18 19 if (IsValid(DizzyEffectAsset.Get())) 20 { 21 UE_LOG(LogTemp, Log, TEXT("DizzyEffectAsset is valid")); 22 } 23 else 24 { 25 UE_LOG(LogTemp, Error, TEXT("DizzyEffectAsset is null, Function name: %s"), *FString(__FUNCTION__)); 26 } 27}

    Next, replace the synchronous asset loading with Get().

    PlayerCharacter.cpp
    1void APlayerCharacter::StartDizzy() 2{ 3 if (IsDizzy) 4 { 5 return; 6 } 7 CharacterMovementComponent->MaxWalkSpeed = DizzySpeed; 8 9 10 IsDizzy = true; 11 12 UNiagaraSystem* DizzyEffectSystem = DizzyEffectAsset.Get(); 13 if (!IsValid(DizzyEffectSystem)) 14 { 15 UE_LOG(LogTemp, Error, TEXT("DizzyEffectSystem is null, Function name: %s"), *FString(__FUNCTION__)); 16 } 17 DizzyEffect = UNiagaraFunctionLibrary::SpawnSystemAttached(DizzyEffectSystem, SceneComponent, NAME_None, 18 DizzyEffectOffset, FRotator::ZeroRotator, 19 EAttachLocation::KeepRelativeOffset, true); 20 21 if (!IsValid(DizzySoundAsset)) 22 { 23 UE_LOG(LogTemp, Error, TEXT("DizzySound is null, Function name: %s"), *FString(__FUNCTION__)); 24 } 25 else 26 { 27 DizzySound = UGameplayStatics::SpawnSoundAtLocation(GetWorld(), DizzySoundAsset, GetActorLocation()); 28 } 29 GetWorldTimerManager().SetTimer(DizzyTimerHandle, this, &APlayerCharacter::EndDizzy, DizzyDuration, false); 30}

    This completes the implementation of asynchronous loading.
    Next, let's verify it in the Standalone game.

    If the log shows DizzyEffectAsset is valid, it confirms that the asynchronous loading (AsyncLoad) of the asset was successful.

    Results

    As a result of using the "stat UnitGraph" command, the spikes on the graph have disappeared, and the hitch has been resolved.

    Summary

    Investigation Process

    • Hitch Detection: We used the "stat unitgraph" command to graph the hitches that occurred during gameplay and identify where heavy processing was taking place.
    • Measurement with Unreal Insight: To investigate the detailed load during hitch occurrences, we collected trace data using the trace.start and trace.stop commands and confirmed the source of the load.

    Problem Identification

    • It was determined that the effect asset (Niagara effect) was being loaded using synchronous loading (LoadSynchronous()), causing other processes to stop until the load was complete, resulting in hitches.

    Implementation of Asynchronous Loading

    • To avoid hitches, we added a process to asynchronously load the effect at the start of the game using TSoftObjectPtr. This ensures that the asset is loaded before the player uses the effect, preventing hitches.

    Result

    The implementation of asynchronous loading confirmed that the hitches were resolved, resulting in smoother gameplay. By using asynchronous loading, we improved game performance and enhanced the player experience.

    1

    Comments

    No comments

    Let's comment your feeling